/* hl2demool - hl2 demo toolbox  */

/* Copyright (c) 2007-2012, ben "boin" bonoeil

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
   * Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above
   copyright notice, this list of conditions and the following
   disclaimer in the documentation and/or other materials provided
   with the distribution.
   * Neither the name of the ben "boin" bonoeil nor the names of its
   contributors may be used to endorse or promote products derived
   from this software without specific prior written permission.

   NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
   GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY
   ben "boin" bonoeil ''AS IS'' AND ANY EXPRESS OR IMPLIED
   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED. IN NO EVENT SHALL ben "boin" bonoeil BE LIABLE FOR ANY
   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "hl2demo_print.h"
#include "hl2demo_parser.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <errno.h>
#include <dirent.h>

enum {
  OUTPUT_XML, OUTPUT_INF, OUTPUT_TXT
};

static const char output_names[4][3] = {
  "xml","inf","txt"
};

const char * me;
const char version[] = VERSION;

static int opt_verbose = 2;
static int opt_debug   = 0;
static int opt_format  = OUTPUT_TXT;

static int dump_fct(FILE *, void *);
static int copy_fct(FILE *, void *);
static int help_fct(FILE *, void *);
static int info_fct(FILE *, void *);
static int test_fct(FILE *, void *);
static int vers_fct(FILE *, void *);

static const char * arg0[] = {0};
static const char * arg1[] = {"<in>",0};
static const char * arg2[] = {"<in>","<out>",0};
static const char * arg3[] = {"<in>","<prefix>",0};

static struct mode_s {
  const char * name;
  const char ** args;
  int (*fct)(FILE *, void *);
  const char * desc;

} modes[] = {
  { "help", arg0, help_fct, "display this help"                         },
  { "vers", arg0, vers_fct, "display version"                           },
  { "test", arg1, test_fct, "test for valid hl2demo file"               },
  { "info", arg1, info_fct, "display info "                             },
  { "copy", arg2, copy_fct, "copy file (with optionnal filter)"         },
  { "dump", arg3, dump_fct, "dump demo packets into separate files"     },
};
#define MAXMODES (sizeof(modes)/sizeof(*modes))

/* static struct option longoptions[] = { */
/*   /\* name        has_arg   *flag   val  *\/ */
/*   { "help",      0,        0       'h' }, */
/*   { "version",   0,        0       'V' }, */
/*   { "debug",     0,        0       'D' }, */
/*   { "verbose",   0,        0       'v' }, */
/*   { "quiet",     0,        0       'q' }, */
/* }; */


static int print_usage(void)
{
  int i;
  static char tabs[] = "                 ";
  printf(
    "Usage: %s [OPTION] action [<input-demo> [<output-demo>]]\n"
    "\n"
    "  An hl2 demo file toolbox.\n"
    "\n"
    " Options:\n"
    "  -h               print this help and exit\n"
    "  -V               print version and copyright and exit\n"
    "  -D               enable debug mode\n"
    "  -v               increase verbosity\n"
    "  -q               decrease verbosity\n"
    "  -t               set plain-text output mode\n"
    "  -i               set info output mode\n"
    "  -x               set XML output mode\n"
    "\n"
    " Actions:\n", me);

  for (i=0 ; i<MAXMODES; ++i) {
    int j = 0, k;
    printf("  %s",modes[i].name);
    for (k=0; modes[i].args[k]; ++k) {
      printf(" %s", modes[i].args[k]); j+= strlen(modes[i].args[k])+1;
    }
    printf("%s%s\n",tabs+j,modes[i].desc);
  }

  puts("\n"
       "Report bugs to <boin.ingame [4t] gmail [d0t] com>");

  return 0;
}

static int print_version(void)
{
  printf("%s %s\n"
         "\n"
         "Copyright (c) 2007-2009, Ben \"Boin\" Bonoeil\n"
         "All rights reserved.\n", me, version);
  return 0;
}

int main(int argc, char * argv[])
{
  int opt, code = 0;
  char * inpname = 0, * outname = 0;
  FILE * inp = 0;
  size_t inplen = 0/* , outlen = 0 */;

  static const char * optstr="hvqxDVitx";

  me = basename(argv[0]);
  opterr = 0;                   /* disable getopt error print */
  while (opt = getopt(argc, argv, optstr), opt != -1) {
    switch (opt) {
    case 'h':                   /* help */
      exit(print_usage());
      break;
    case 'v':                   /* verbose */
      ++opt_verbose;
      break;
    case 'q':                   /* quiet */
      opt_verbose -= opt_verbose>0;
      break;
    case 'D':                   /* debug */
      ++opt_debug;
      break;
    case 'V':                   /* version */
      exit(print_version());
      break;
    case 'x':                   /* XML output */
      opt_format = OUTPUT_XML;
      break;
    case 'i':                   /* INF output */
      opt_format = OUTPUT_INF;
      break;
    case 't':                   /* TXT output */
      opt_format = OUTPUT_TXT;
      break;
    default:
      fprintf(stderr, "%s: invalid option -- '%c' (try -h)\n", me, optopt);
      exit(1);
    }
  }

  if (optind >= argc) {
    code = 127;
    fprintf(stderr, "%s: missing action (try -h)\n", me);
  } else do {
      int i, j;
      const char * modestr = argv[optind++];
      for (i=0; i<MAXMODES && strcmp(modestr, modes[i].name); ++i)
        ;
      if (i == MAXMODES) {
        fprintf(stderr, "%s: invalid action -- '%s' (try -h)\n", me, modestr);
        code = 127;
        break;
      }

      for (j=0; modes[i].args && modes[i].args[j]; ++j)
        ;

      if (optind + j != argc) {
        fprintf(stderr,
                "%s: invalid argument count for action -- '%s' (try -h)\n",
                me, modestr);
        code = 127;
        break;
      }

      if (optind < argc) {
        inpname = argv[optind++];
        if (!inpname[0]) {
          error("%s: invalid filename\n",me);
          code = 127;
          break;
        }
      }

      if (optind < argc) {
        outname = argv[optind++];
        if (!outname[0]) {
          error("%s: invalid filename\n",me);
          code = 127;
          break;
        }
      }

      msgfile = opt_verbose >= 2 ? stdout : 0;
      dbgfile = opt_debug        ? stdout : 0;
      errfile = opt_verbose      ? stderr : 0;
      wrnfile = opt_verbose > 1  ? stderr : 0;

      if (inpname) {
        inp = fopen(inpname, "rb");
        if (!inp) {
          error("%s: %s (%s)\n", me, strerror(errno), inpname);
          code = 127;
          break;
        }
        if (!fseek(inp,0L,SEEK_END)) {
          inplen = ftell(inp);
        }
        if (fseek(inp,0L,SEEK_SET)) {
          error("%s: %s (%s)\n", me, strerror(errno), inpname);
          code = 127;
          break;
        }
        debug("%s has %d bytes\n", basename(inpname), inplen);
      } else {
      }

      switch (opt_format) {
      case OUTPUT_XML: case OUTPUT_INF: case OUTPUT_TXT:
        break;
      default:
        error("INTERNAL ERROR: invalid output format (%d)\n",opt_format);
        code = 66;
        break;
      }
      debug ("Output-mode : %s\n",output_names[opt_format]);
      debug ("Action      : %s\n",modes[i].name);
      debug ("Input       : %s\n",inpname?inpname:"n/a");
      debug ("Output      : %s\n",outname?outname:"n/a");

      code = modes[i].fct(inp,outname);
    } while (0);

  return code;
}

static int help_fct(FILE * inp, void * dummy) {
  return print_usage();
}

static int vers_fct(FILE * inp, void * dummy) {
  return print_version();
}

static int test_fct(FILE *inp, void * dummy) {
  int err;
  hl2demo_t dem;

  do {
    err = hl2demo_header_read(&dem.header, inp);
    if (err < 0) break;
    err =  hl2demo_parser(&dem, inp, PARSER_SKIP_ALL);
    if (err) break;
  } while (0);

  return err;
}


static int info_fct(FILE *inp, void * arg2) {
  int err;
  hl2demo_t dem;
  FILE *out = arg2;

  out = out;


  do {
    err = hl2demo_header_read(&dem.header, inp);
    if (err) break;

    switch (opt_format) {
    case OUTPUT_XML:
      break;
    case OUTPUT_INF:
      break;
    case OUTPUT_TXT:
      break;
    }

    err =  hl2demo_parser(&dem, inp, PARSER_SKIP_NONE);
    if (err) break;
  } while (0);

  debug("%s: exiting with %d\n", me, err);

  return err;
}

static int copy_fct(FILE *inp, void * dummy) {
  FILE *out = dummy;
  out = out;

  return -1;
}

struct dump_t {
  FILE * inp;
  const char * prefix;
};

static int dump_cb( hl2demo_message_t * msg, void * cookie )
{
  struct dump_t * dump = cookie;
  FILE * out = 0;
  int err = -1;
  char * filename = 0;
  asprintf(&filename,
           "%s%06x-%06x-%X",
           dump->prefix, msg->cnt, msg->tck, msg->cmd);
  if (!filename) {
    error("%s: %s (%s)\n", me, strerror(errno), "dump-callback");
    goto cleanexit;
  }
  message ("dumping into %s",  filename);
  message (" (%d bytes)\n", msg->len);

  out = fopen(filename, "wb");
  if (!out) {
    error("%s: %s (%s)\n", me, strerror(errno), filename);
    goto cleanexit;
  }

  if ( msg->len != fwrite(msg->buf, 1, msg->len, out )) {
    error("%s: %s (%s)\n", me, strerror(errno), filename);
    goto cleanexit;
  }
  err = 0;
cleanexit:

  free(filename);
  if (out) fclose(out);

  return err;
}

static int dump_fct(FILE *inp, void * arg2) {
  struct dump_t dump;
  hl2demo_t dem;
  int err = 0;

  dump.inp = inp;
  dump.prefix = arg2;

  message ("dumping demo into '%s'\n", dump.prefix);
  hl2demo_parser_callback(dump_cb, &dump );
  do {
    err = hl2demo_header_read(&dem.header, inp);
    if (err) break;

    err =  hl2demo_parser(&dem, inp, PARSER_SKIP_NONE);
    if (err) break;
  } while (0);

  return err;
}
